﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace EffiProz.Core.Lib.Tar
{
    /**
  * Works with tar archives containing HSQLDB database instance backups.
  * Viz, creating, examining, or extracting these archives.
  * <P/>
  * This class provides OO Tar backup-creation control.
  * The extraction and listing features are implemented only in static fashion
  * in the Main method, which provides a consistent interface for all three
  * features from the command-line.
  * <P/>
  * For tar creation, the default behavior is to fail if the target archive
  * exists, and to abort if any database change is detected.
  * Use the JavaBean setters to changes this behavior.
  * <P/>
  * See the main(String[]) method for details about command-line usage.
  *
  * @see <a href="../../../../../guide/deployment-chapt.html#deployment_backup-sect"
  *      target="guide">
  *     The database backup section of the HyperSQL User Guide</a>
  * @see #main(String[])
  * @see #setOverWrite(bool)
  * @see #setAbortUponModify(bool)
  * @author Blaine Simpson (blaine dot simpson at admc dot com)
  */
    public class DbBackup
    {



        /**
         * Instantiate a DbBackup instance for creating a Database Instance backup.
         *
         * Much validation is deferred until the write() method, to prevent
         * problems with files changing between the constructor and the write call.
         */
        public DbBackup(FileInfo archiveFile, String dbPath)
        {

            this.archiveFile = archiveFile;

            FileInfo dbPathFile = new FileInfo(dbPath);

            dbDir = dbPathFile.Directory;
            instanceName = dbPathFile.Name;
        }

        protected DirectoryInfo dbDir;
        protected FileInfo archiveFile;
        protected String instanceName;
        protected bool overWrite = false;    // Defaults no NO OVERWRITE
        protected bool abortUponModify = true;     // Defaults to ABORT-UPON-MODIFY

        /**
         * Defaults to false.
         *
         * If false, then attempts to write a tar file that already exist will
         * abort.
         */
        public void setOverWrite(bool overWrite)
        {
            this.overWrite = overWrite;
        }

        /**
         * Defaults to true.
         *
         * If true, then the write() method will validate that the database is
         * closed, and it will verify that no DB file changes between when we
         * start writing the tar, and when we finish.
         */
        public void setAbortUponModify(bool abortUponModify)
        {
            this.abortUponModify = abortUponModify;
        }

        public bool getOverWrite()
        {
            return overWrite;
        }

        public bool getAbortUponModify()
        {
            return abortUponModify;
        }

        /**
         * This method always backs up the .properties and .script files.
         * It will back up all of .backup, .data, and .log which exist.
         *
         * If abortUponModify is set, no tar file will be created, and this
         * method will throw.
         *
         * @throws IOException for any of many possible I/O problems
         * @throws IllegalStateException only if abortUponModify is set, and
         *                               database is open or is modified.
         */
        public void write()
        {

            FileInfo propertiesFile = new FileInfo(dbDir.FullName + Path.DirectorySeparatorChar + instanceName + ".properties");
            FileInfo scriptFile = new FileInfo(dbDir.FullName + Path.DirectorySeparatorChar + instanceName + ".script");
            FileInfo[] componentFiles = new FileInfo[] {
            propertiesFile, scriptFile,
            new FileInfo(dbDir.FullName+Path.DirectorySeparatorChar+ instanceName + ".backup"),
            new FileInfo(dbDir.FullName+Path.DirectorySeparatorChar+ instanceName + ".data"),
            new FileInfo(dbDir.FullName+Path.DirectorySeparatorChar+instanceName + ".log"),
            new FileInfo(dbDir.FullName+Path.DirectorySeparatorChar+ instanceName + ".lobs")
        };
            bool[] existList = new bool[componentFiles.Length];
            long startTime = DateTime.Now.Ticks;

            for (int i = 0; i < existList.Length; i++)
            {
                existList[i] = componentFiles[i].Exists;

                if (!existList[i])
                {
                    if (i < 2)
                    {

                        // First 2 files are REQUIRED
                        throw new FileNotFoundException(
                               String.Format("Required file is missing: {0}", componentFiles[i].FullName));
                    }
                    else
                    {
                        componentFiles[i] = null;
                    }
                }
            }

            if (abortUponModify)
            {
                Properties p = new Properties();

                p.Load(propertiesFile.OpenRead());

                String modifiedString = p.GetProperty("modified");

                if (modifiedString != null
                        && (modifiedString.Equals("yes", StringComparison.OrdinalIgnoreCase)
                            || modifiedString.Equals("true", StringComparison.OrdinalIgnoreCase)))
                {
                    throw new InvalidOperationException(String.Format("'modified' DB property is {0}", modifiedString));
                }
            }

            if (overWrite && archiveFile.Exists)
                archiveFile.Delete();

            using (FileStream archUsTar = archiveFile.Create())
            {
                using (TarWriter legacyTar = new TarWriter(archUsTar))
                {
                    for (int i = 0; i < componentFiles.Length; i++)
                    {
                        if ( componentFiles[i] == null || !componentFiles[i].Exists)
                        {
                            continue;

                            // We've already verified that required files exist, therefore
                            // there is no error condition here.
                        }

                        legacyTar.Write(componentFiles[i].FullName, componentFiles[i].Name);
                    }
                }
            }


            if (abortUponModify)
            {
                try
                {
                    for (int i = 0; i < componentFiles.Length; i++)
                    {
                        if (componentFiles[i].Exists)
                        {
                            if (!existList[i])
                            {
                                throw new FileNotFoundException(
                                    String.Format("{0} disappeared after backup started",
                                        componentFiles[i].FullName));
                            }

                            if (componentFiles[i].LastWriteTime.Ticks > startTime)
                            {
                                throw new FileNotFoundException(
                                   String.Format("{0} changed after backup started",
                                        componentFiles[i].FullName));
                            }
                        }
                        else if (existList[i])
                        {
                            throw new FileNotFoundException(
                                String.Format("{0}  appeared after backup started",

                                    componentFiles[i].FullName));
                        }
                    }
                }
                catch (InvalidOperationException ise)
                {
                    archiveFile.Delete();
                    throw ise;
                }
            }
        }

    }
}
